/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ro.nextreports.server.security;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.MappingSqlQuery;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import ro.nextreports.server.domain.User;
/**
* @author Decebal Suiu
*/
public class DatabaseExternalUsersService implements ExternalUsersService, UserDetailsService, InitializingBean {
public static final String DEFAULT_USER_QUERY = "SELECT username, password FROM users WHERE username = ?";
private static final Logger LOG = LoggerFactory.getLogger(DatabaseExternalUsersService.class);
protected DataSource dataSource;
protected String userNamesQuery;
protected String groupNamesQuery; // it's optional
protected String userQuery = DEFAULT_USER_QUERY;
protected Map<String, String> mapping; // database column (value) to user property (key)
private MappingSqlQuery userByUsernameMapping;
private JdbcTemplate jdbcTemplate;
public DataSource getDataSource() {
return dataSource;
}
@Required
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
@Required
public void setUserNamesQuery(String userNamesQuery) {
this.userNamesQuery = userNamesQuery;
}
public void setGroupNamesQuery(String groupNamesQuery) {
this.groupNamesQuery = groupNamesQuery;
}
public String getUserQuery() {
return userQuery;
}
public void setUserQuery(String userQuery) {
this.userQuery = userQuery;
}
public Map<String, String> getMapping() {
return mapping;
}
@Required
public void setMapping(Map<String, String> mapping) {
this.mapping = mapping;
}
public boolean supports(Class authentication) {
return NextServerAuthentication.class.isAssignableFrom(authentication);
}
@SuppressWarnings("unchecked")
public List<String> getUserNames() {
return jdbcTemplate.query(userNamesQuery, new RowMapper() {
public Object mapRow(ResultSet resultSet, int rowNum) throws SQLException {
return resultSet.getString(1);
}
});
}
@SuppressWarnings("unchecked")
public User getUser(String username) throws UsernameNotFoundException, DataAccessException {
List<User> users = userByUsernameMapping.execute(username);
if (users.size() == 0) {
throw new UsernameNotFoundException("User '" + username + "' not found");
}
User user = (User) users.get(0);
return user;
}
@SuppressWarnings("unchecked")
public List<String> getGroupNames(String username) {
if (groupNamesQuery == null) {
// TODO !? null if you want to deleteGroups on sync
return Collections.emptyList();
}
return jdbcTemplate.query(groupNamesQuery, new String[] { username }, new RowMapper() {
public Object mapRow(ResultSet resultSet, int rowNum) throws SQLException {
return resultSet.getString(1);
}
});
}
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
return getUser(username);
}
public void afterPropertiesSet() throws Exception {
userByUsernameMapping = new UserByUsernameMapping(getDataSource());
jdbcTemplate = new JdbcTemplate(getDataSource());
if (LOG.isDebugEnabled()) {
LOG.debug("mapping = " + mapping);
}
}
/**
* Query object to look up a user.
*/
protected class UserByUsernameMapping extends MappingSqlQuery {
protected UserByUsernameMapping(DataSource dataSource) {
super(dataSource, userQuery);
declareParameter(new SqlParameter(Types.VARCHAR));
compile();
}
protected Object mapRow(ResultSet resultSet, int rowNum) throws SQLException {
User user = new User();
// required
// TODO test with readable exception
String username = resultSet.getString(mapping.get("user.username"));
user.setUsername(username);
// required
// TODO test with readable exception
String password = "c4ca4238a0b923820dcc509a6f75849b"; // ?! 1 (see cas where I dont have password in database)
String passwordColumn = mapping.get("user.password");
if (!StringUtils.isEmpty(passwordColumn)) {
password = resultSet.getString(passwordColumn);
}
user.setPassword(password);
// TODO cache !!
List<String> columnNames = getColumnNames(resultSet);
// optional
if (mapping.containsKey("user.enabled")) {
String column = mapping.get("user.enabled");
if (columnNames.contains(column)) {
user.setEnabled(resultSet.getBoolean(column));
}
}
// optional
if (mapping.containsKey("user.admin")) {
String column = mapping.get("user.admin");
if (columnNames.contains(column)) {
user.setAdmin(resultSet.getBoolean(column));
}
}
// optional
if (mapping.containsKey("user.email")) {
String column = mapping.get("user.email");
if (columnNames.contains(column)) {
user.setEmail(resultSet.getString(column));
}
}
// optional
if (mapping.containsKey("user.realName")) {
String column = mapping.get("user.realName");
if (columnNames.contains(column)) {
user.setRealName(resultSet.getString(column));
}
}
// optional
if (mapping.containsKey("user.profile")) {
String column = mapping.get("user.profile");
if (columnNames.contains(column)) {
user.setProfile(resultSet.getString(column));
}
}
return user;
}
private List<String> getColumnNames(ResultSet resultSet) throws SQLException {
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
int columnCount = resultSetMetaData.getColumnCount();
List<String> columnNames = new ArrayList<String>();
for (int i = 1; i <= columnCount; i++) {
columnNames.add(resultSetMetaData.getColumnName(i));
}
return columnNames;
}
}
}